/************************************
 * Package: RGcpp					*
 * Authors: Rui Eduardo Gouveia Gil	*
 * Date: 2010						*
 ************************************/
#include "rgcpp/net/ssl/DynamicSocket.h"

namespace rgcpp
{

DynamicSocket::DynamicSocket(SSLContext* sslContext, const int socketFD, bool mode) : TCPSocket(socketFD), ssl(NULL), sslMode(mode), serverSide(true)
{
	boost::mutex::scoped_lock lock(SSLContext::mut);

	this->sslContext=sslContext;

	if(sslMode)
	{
		ERR_clear_error();
		this->ssl = SSL_new(sslContext->getContext());
		SSL_set_fd(ssl,socketFD);

		ERR_clear_error();
		if(SSL_accept(ssl) < 1)
		{
			if(ssl)
			{
				SSL_shutdown(ssl);
				SSL_free(ssl);
			}
#ifdef _WIN32
			shutdown(socketFD,SD_BOTH);
			closesocket(socketFD);
#else
			shutdown(socketFD,SHUT_RDWR);
			close(socketFD);
#endif
			Throw(SocketException(std::string("Unable to set ssl protocol - ")+ERR_error_string(SSL_get_verify_result(ssl),NULL)));
		}
	}
}

DynamicSocket::DynamicSocket(SSLContext* sslContext, const IPAddress& ipAddress, bool mode) : TCPSocket(ipAddress), ssl(NULL), sslMode(mode), serverSide(false)
{
	boost::mutex::scoped_lock lock(SSLContext::mut);

	this->sslContext = sslContext;

	if(sslMode)
	{
		ERR_clear_error();
		this->ssl = SSL_new(sslContext->getContext());
		SSL_set_fd(ssl,socketFD);

		ERR_clear_error();
		if(SSL_connect(ssl) < 1)
		{
			if(ssl)
			{
				SSL_shutdown(ssl);
				SSL_free(ssl);
			}
#ifdef _WIN32
			shutdown(socketFD,SD_BOTH);
			closesocket(socketFD);
#else
			shutdown(socketFD,SHUT_RDWR);
			close(socketFD);
#endif
			Throw(SocketException(std::string("Unable to set ssl protocol - ")+ERR_error_string(SSL_get_verify_result(ssl),NULL)));
		}
	}
}

DynamicSocket::~DynamicSocket()
{
	boost::mutex::scoped_lock lockRead(mutRead);
	boost::mutex::scoped_lock lockWrite(mutWrite);

	ERR_clear_error();
	if(ssl)
	{
		SSL_shutdown(ssl);
		SSL_free(ssl);
	}
}

int DynamicSocket::readData(char* buffer, const unsigned int size)
{
	if(sslMode)
	{
		Throw(IllegalStateException("Invalid mode!!!!"));
	}

	return TCPSocket::readData(buffer,size);
}

void DynamicSocket::writeData(const char* buffer, const unsigned int size)
{
	if(!size)
		return;

	if(sslMode)
	{
		Throw(IllegalStateException("Invalid mode!!!!"));
	}

	TCPSocket::writeData(buffer,size);
}

int DynamicSocket::readEncryptedData(char* buffer, const unsigned int length)
{
	if(!sslMode)
	{
		Throw(IllegalStateException("Invalid mode!!!!"));
	}

	boost::mutex::scoped_lock lock(mutRead);

	if(!SSL_pending(ssl))
	{
		fd_set fdList;

		FD_ZERO(&fdList);
		FD_SET(socketFD, &fdList);

		int value;
		struct timeval timeout = this->timeout;
		do
		{
			if(!timeout.tv_usec && !timeout.tv_sec)
				value = ::select(socketFD+1,&fdList,NULL,NULL,NULL);
			else
				value = ::select(socketFD+1,&fdList,NULL,NULL,&timeout);
		}while(value == -1&& errno == EINTR);

		if(value == 0)
		{
			Throw(SocketException(std::string("Error reading from ssl socket - Timeout exception")));
		}
		else if(value == -1)
		{
			Throw(SocketException(std::string("Error reading from ssl socket - ")+strerror(errno)));
		}

		if(this->releaseForced)
			return 0;
	}

	int readSize = SSL_read(ssl, buffer, length);

	if(readSize<1)
	{
		Throw(SocketException("Error reading from ssl socket"));
	}

	return readSize;
}

void DynamicSocket::writeEncryptedData(const char* buffer, const unsigned int length)
{
	if(!length)
		return;

	if(!sslMode)
	{
		Throw(IllegalStateException("Invalid mode!!!!"));
	}

	boost::mutex::scoped_lock lockWrite(mutWrite);

	int writeSize = SSL_write(ssl, buffer, length);

	if(writeSize<1)
	{
		Throw(SocketException("Error while writing to ssl socket"));
	}

#ifndef _WIN32
	fsync(socketFD);
#endif
}

void DynamicSocket::upgradeToSSL()
{
	if(sslMode)
		return;

	boost::mutex::scoped_lock lock(SSLContext::mut);

	this->ssl = SSL_new(sslContext->getContext());
	SSL_set_fd(ssl,socketFD);

	if(serverSide)
	{
		if(SSL_accept(ssl) < 1)
		{
			if(ssl)
			{
				SSL_shutdown(ssl);
				SSL_free(ssl);
			}
			Throw(SocketException(std::string("Unable to set ssl protocol - ")+ERR_error_string(SSL_get_verify_result(ssl),NULL)));
		}
	}
	else
	{
		if(SSL_connect(ssl) < 1)
		{
			if(ssl)
			{
				SSL_shutdown(ssl);
				SSL_free(ssl);
			}
			Throw(SocketException(std::string("Unable to set ssl protocol - ")+ERR_error_string(SSL_get_verify_result(ssl),NULL)));
		}
	}

	sslMode = true;
}

void DynamicSocket::downgradeFromSSL()
{
	if(!sslMode)
		return;

	boost::mutex::scoped_lock lockRead(mutRead);

	if(serverSide)
	{
		char buffer[1];
		if(SSL_read(ssl,buffer,1))
		{
			Throw(SocketException("Invalid socket downgrade: read"));
		}

		SSL_shutdown(ssl);
	}
	else
	{
		SSL_shutdown(ssl);

		char buffer[1];
		if(SSL_read(ssl,buffer,1))
		{
			Throw(SocketException("Invalid socket downgrade: read"));
		}
	}

	SSL_free(ssl);

	this->ssl=NULL;

	sslMode = false;
}

bool DynamicSocket::isOnSSLMode()
{
	return sslMode;
}

bool DynamicSocket::isCertifiedConnection()
{
	if(!sslMode)
		return false;
	return (SSL_get_verify_result(ssl)==X509_V_OK && SSL_get_peer_certificate(ssl)!=NULL);
}

}
